package cz.cvut.fit.mi_paa.knapsack.strategy;

import cz.cvut.fit.mi_paa.abs.Context;
import cz.cvut.fit.mi_paa.knapsack.KnapsackInstance;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created with IntelliJ IDEA.
 * User: milos
 * Date: 22.1.13
 * Time: 1:47
 * To change this template use File | Settings | File Templates.
 */
public class SimulatedAnnealingKnapsackStrategy extends KnapsackStrategy {

    private double finalTemp;
    private double temperatureCoeficient;
    private double initialTemperature;
    private double innerCycleIterationsMaxCount;
    private Random random = new Random();
    private boolean[] best;
    private boolean[] actualSolution;
    private int bestPrice;
    private KnapsackInstance instance;

    public SimulatedAnnealingKnapsackStrategy(Context context) {
        super(context);
        instance = (KnapsackInstance) context.getInstance();
        best = new boolean[instance.getItemNumber()];
        actualSolution = new boolean[instance.getItemNumber()];
    }

    @Override
    public void solve() throws IOException, CloneNotSupportedException {
        context.getSw().reset();
        context.getSw().start();
        double actualTemp=initialTemperature;
        int innerCycleIterationsCounter=0;
        int delta=0;
        int sol=0;

        do {
            innerCycleIterationsCounter=0;
            do {
                int index = random.nextInt(instance.getItemNumber());
                if(!actualSolution[index]) {
                    actualSolution[index] = !actualSolution[index];
                    int i = 0;
                    while (getConfigurationWeight(actualSolution)>instance.getCapacity()) {
                        dropRandomItem(actualSolution);
                    }
                    delta = getConfigurationCost(actualSolution) - getConfigurationCost(best);
                    if(delta >= 0 || Math.random()< Math.exp((delta/actualTemp))) System.arraycopy(actualSolution,0,best,0,actualSolution.length);
                } else {
                    actualSolution[index] = !actualSolution[index];
                    delta = getConfigurationCost(actualSolution) - getConfigurationCost(best);
                    if(delta >= 0 || Math.random()< Math.exp((delta/actualTemp))) System.arraycopy(actualSolution,0,best,0,actualSolution.length);
                }
                if (sol < getConfigurationCost(best)) sol = getConfigurationCost(best);
                innerCycleIterationsCounter++;
            } while (innerCycleIterationsMaxCount>innerCycleIterationsCounter);
            actualTemp *= temperatureCoeficient;
        } while (actualTemp >= finalTemp);
        bestPrice = sol;
        context.getSw().stop();
    }

    private void dropRandomItem(boolean[] array) {
        List<Integer> takenItemsIndexes = new ArrayList<Integer>();
        for (int i=0; i<array.length ; i++) {
            if(array[i]) takenItemsIndexes.add(i);
        }
        int index = random.nextInt(takenItemsIndexes.size());
        array[takenItemsIndexes.get(index)] = !array[takenItemsIndexes.get(index)];
    }

    private int getConfigurationCost(boolean[] array) {
        int cost=0;
        for (int i = 0; i< array.length;i++) {
            if(array[i]) cost+=instance.getItems()[i].getPrice();
        }
        return cost;
    }

    private int getConfigurationWeight(boolean[] array) {
        int weight=0;
        for (int i = 0; i< array.length;i++) {
            if(array[i]) weight+=instance.getItems()[i].getWeight();
        }
        return weight;
    }

    @Override
    public void setArgs(String[] args) {
        finalTemp = Double.valueOf(args[0]);
        temperatureCoeficient = Double.valueOf(args[1]);
        initialTemperature = Double.valueOf(args[2]) * instance.getHighestPrice();
        innerCycleIterationsMaxCount = instance.getItems().length * Double.valueOf(args[3]);
    }

    @Override
    public void writeResult() throws IOException {
        FileWriter resultWriter = context.getResultWriter();
        resultWriter.write(instance.getId() + " ");
        resultWriter.write(context.getSw().getTimeMillis() + " ");
        resultWriter.write(instance.getItemNumber() + " ");
        resultWriter.write(bestPrice+ "  ");

        for (int i = 1; i<best.length;i++) {
            if(i!=(best.length-1))
                resultWriter.write(best[i]+" ");
            else
                resultWriter.write(best[i]+"\n");
        }
    }
}
